﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using Newtonsoft.Json.Linq;

using StackExchange.Redis;

namespace Jack.RedisLib
{
    public partial class RedisClient
    {
        public async Task<T> ListGetByIndex<T>(string key, long index, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.ListGetByIndexAsync(redisKey, index);

            return ConvertRedisValueToObjectValue<T>(redisValue);
        }

        public async Task<T?> ListGetByIndexForValueType<T>(string key, long index, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.ListGetByIndexAsync(redisKey, index);

            return ConvertRedisValueToValueTypeValue<T>(redisValue);
        }

        public async Task<long> ListInsertAfter<T>(string key, T pivot, T value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return -1;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue pivotRedisValue = ConvertObjectValueToRedisValue(pivot);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);

            return await database.ListInsertAfterAsync(redisKey, pivotRedisValue, redisValue);
        }

        public async Task<long> ListInsertBefore<T>(string key, T pivot, T value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return -1;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue pivotRedisValue = ConvertObjectValueToRedisValue(pivot);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);

            return await database.ListInsertBeforeAsync(redisKey, pivotRedisValue, redisValue);
        }

        public async Task<T> ListLeftPop<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.ListLeftPopAsync(redisKey);

            return ConvertRedisValueToObjectValue<T>(redisValue);
        }

        public async Task<T[]> ListLeftPop<T>(string key, long count, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.ListLeftPopAsync(redisKey, count);
            if (redisValues == null || redisValues.Length == 0) return [];

            T[] values = new T[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToObjectValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T?> ListLeftPopForValueType<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.ListLeftPopAsync(redisKey);

            return ConvertRedisValueToValueTypeValue<T>(redisValue);
        }

        public async Task<T?[]> ListLeftPopForValueType<T>(string key, long count, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.ListLeftPopAsync(redisKey, count);
            if (redisValues == null || redisValues.Length == 0) return [];

            T?[] values = new T?[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToValueTypeValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<long> ListLeftPush<T>(string key, T value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return -1;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);

            return await database.ListLeftPushAsync(redisKey, redisValue);
        }

        public async Task<long> Enqueue<T>(string key, T value, int db = -1, CancellationToken cancellationToken = default) => await ListLeftPush(key, value, db, cancellationToken).ConfigureAwait(false);

        public async Task<long> ListLeftPush<T>(string key, List<T> values, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return -1;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = BatchConvertObjectValueToRedisValue(values);

            return await database.ListLeftPushAsync(redisKey, redisValues);
        }

        public async Task<long> Enqueue<T>(string key, List<T> values, int db = -1, CancellationToken cancellationToken = default) => await ListLeftPush(key, values, db, cancellationToken).ConfigureAwait(false);

        public async Task<long> ListLength(string key, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return 0;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.ListLengthAsync(redisKey);
        }

        public async Task<T[]> ListRange<T>(string key, long start = 0L, long stop = -1L, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.ListRangeAsync(redisKey, start, stop);
            if (redisValues == null || redisValues.Length == 0) return [];

            T[] values = new T[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToObjectValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T?[]> ListRangeForValueType<T>(string key, long start = 0L, long stop = -1L, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.ListRangeAsync(redisKey, start, stop);
            if (redisValues == null || redisValues.Length == 0) return [];

            T?[] values = new T?[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToValueTypeValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<long> ListRemove<T>(string key, T value, long count = 0L, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return 0;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);

            return await database.ListRemoveAsync(redisKey, redisValue, count);
        }

        public async Task<T> ListRightPop<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.ListRightPopAsync(redisKey);

            return ConvertRedisValueToObjectValue<T>(redisValue);
        }

        public async Task<T> Dequeue<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : class => await ListRightPop<T>(key, db, cancellationToken);

        public async Task<T[]> ListRightPop<T>(string key, long count, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.ListRightPopAsync(redisKey, count);
            if (redisValues == null || redisValues.Length == 0) return [];

            T[] values = new T[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToObjectValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T[]> Dequeue<T>(string key, long count, int db = -1, CancellationToken cancellationToken = default)
            where T : class => await ListRightPop<T>(key, count, db, cancellationToken);

        public async Task<T?> ListRightPopForValueType<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = await database.ListRightPopAsync(redisKey);

            return ConvertRedisValueToValueTypeValue<T>(redisValue);
        }

        public async Task<T?> DequeueForValueType<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : struct => await ListRightPopForValueType<T>(key, db, cancellationToken);

        public async Task<T?[]> ListRightPopForValueType<T>(string key, long count, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.ListRightPopAsync(redisKey, count);
            if (redisValues == null || redisValues.Length == 0) return [];

            T?[] values = new T?[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToValueTypeValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T?[]> DequeueForValueType<T>(string key, long count, int db = -1, CancellationToken cancellationToken = default)
            where T : struct => await ListRightPopForValueType<T>(key, count, db, cancellationToken);

        public async Task<long> ListRightPush<T>(string key, T value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return -1;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);

            return await database.ListRightPushAsync(redisKey, redisValue);
        }

        public async Task<long> ListRightPush<T>(string key, List<T> values, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return -1;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = BatchConvertObjectValueToRedisValue(values);

            return await database.ListRightPushAsync(redisKey, redisValues);
        }

        public async Task<bool> ListSetByIndex<T>(string key, long index, T value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);
            await database.ListSetByIndexAsync(redisKey, index, redisValue);
            return true;
        }

        public async Task<bool> ListTrim<T>(string key, long start, long stop, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            await database.ListTrimAsync(redisKey, start, stop);
            return true;
        }
    }
}
